package com.bestcem.xm.member.handler.imports;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.enums.WriteDirectionEnum;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.bestcem.xm.common.core.constant.AppRequestErrorCode;
import com.bestcem.xm.common.core.constant.AppResourceErrorCode;
import com.bestcem.xm.common.core.domain.web.ServiceResult;
import com.bestcem.xm.common.core.enums.ResourceErrorCodeEnum;
import com.bestcem.xm.common.core.enums.ValidationErrorCodeEnum;
import com.bestcem.xm.common.core.uitls.DateUtil;
import com.bestcem.xm.common.core.uitls.FileUtil;
import com.bestcem.xm.common.core.uitls.StringUtil;
import com.bestcem.xm.componet.redis.service.RedisService;
import com.bestcem.xm.label.dao.LabelValueDao;
import com.bestcem.xm.label.entity.pojo.LabelValueDO;
import com.bestcem.xm.member.constant.ColumnConstant;
import com.bestcem.xm.member.constant.Constants;
import com.bestcem.xm.member.dao.ExtendFieldDao;
import com.bestcem.xm.member.dao.ExtendFieldEnumItemDao;
import com.bestcem.xm.member.dao.ImportRecordDao;
import com.bestcem.xm.member.dao.MemberDao;
import com.bestcem.xm.member.entity.pojo.ExtendFieldDO;
import com.bestcem.xm.member.entity.pojo.ExtendFieldEnumItemDO;
import com.bestcem.xm.member.entity.pojo.ImportRecordDo;
import com.bestcem.xm.member.enums.ImportRecordStatusEnum;
import com.bestcem.xm.member.enums.MemberTypeEnum;
import com.bestcem.xm.member.grpc.client.XmMemberClient;
import com.bestcem.xm.member.grpc.service.param.imports.ImportMembersParam;
import com.bestcem.xm.member.handler.imports.easyexcel.ReadTemplateListener;
import com.bestcem.xm.member.service.ImportService;
import com.bestcem.xm.member.service.MemberService;
import com.bestcem.xm.member.service.dto.FieldDTO;
import com.bestcem.xm.member.service.dto.LabelDTO;
import com.bestcem.xm.member.service.dto.LabelValueDTO;
import com.bestcem.xm.member.service.dto.OrganizationDTO;
import com.bestcem.xm.member.service.param.ImportMemberParam;
import com.bestcem.xm.member.service.param.ImportProjectParam;
import com.bestcem.xm.member.util.AsyncUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.bestcem.xm.member.constant.Constants.PRIMARY_KEY_MAP;

/**
 * 导入基类
 *
 * @author yf.wang <yf.wang@idiaoyan.com>
 * @version V1.0
 * @date 2021/7/20
 */

@Getter
@Slf4j
public abstract class BaseImportHandle {

    @Resource
    protected RedisService redisService;

    @Resource
    protected ImportRecordDao importRecordDao;

    @Resource
    protected MemberDao memberDao;

    @Resource
    protected MemberService memberService;

    @Resource
    protected ImportService importService;

    @Resource
    protected LabelValueDao labelValueDao;

    @Resource
    protected ExtendFieldDao extendFieldDao;

    @Resource
    protected ExtendFieldEnumItemDao extendFieldEnumItemDao;

    @Resource
    protected XmMemberClient xmMemberClient;

    /**
     * 获取标签名列表
     *
     * @param orgId
     * @return
     */
    public List<String> getLabelName(String orgId) {
        List<LabelValueDO> labelValues = labelValueDao.listByOrgIdAndMemberType(orgId, 0);
        if (CollUtil.isNotEmpty(labelValues)) {
            // 获取标签名列表
            Set<String> labelNames =
                    labelValues.stream().map(labelValue -> labelValue.getLabelName()).collect(Collectors.toSet());
            List<String> result = new ArrayList<>(labelNames.size());
            result.addAll(labelNames);
            return result;
        }
        return new ArrayList<>();
    }

    /**
     * 获取扩展字段名称列表
     *
     * @param orgId
     * @param attribute
     * @return
     */
    public List<String> getFieldNames(String orgId, Integer attribute) {
        List<ExtendFieldDO> extendFields = extendFieldDao.listMemberExtendField(orgId, attribute);
        if (CollUtil.isNotEmpty(extendFields)) {
            List<String> fieldNames = extendFields.stream().map(f -> f.getName()).collect(Collectors.toList());
            return fieldNames;
        }
        return new ArrayList<>();
    }

    /**
     * 生成导入 key
     *
     * @param orgId
     * @return
     */
    public abstract String genImportLockKey(String orgId);

    /**
     * 获取模板的表头文件
     *
     * @return
     */
    public List<String> getTemplateHeaders(String excelPath) throws IOException {
        List<String> headers = new ArrayList<>();
        ClassPathResource resource = new ClassPathResource(excelPath);
        ReadTemplateListener listener = new ReadTemplateListener();
        listener.setHeaders(headers);
        EasyExcel.read(resource.getInputStream(), listener).doReadAll();
        return headers;
    }

    /**
     * 根据文件路径导入联系人
     *
     * @param param
     * @return
     */
    public ServiceResult<String> ImportMember(ImportMembersParam param) {
        // 先检查是否存在已有的任务
        String key = genImportLockKey(param.getOrgId());
        // 预先生成导入记录 ObjectId
        String importRecordId = genImportRecordId();
        param.setRecordId(importRecordId);
        // 检查是否有导入任务
        if (checkImportTask(key)) {
            return ServiceResult.fail("当前已有联系人的导入任务正在执行中，请稍后再试");
        }
        // 加锁
        lockImport(key, importRecordId);
        // 对主键进行校验
        if(StrUtil.isEmpty(param.getPrimaryKey())){
            return ServiceResult.fail("invalid primary_key");
        }
        switch (param.getPrimaryKey()){
            case ColumnConstant.Member.MOBILE:
            case ColumnConstant.Member.EMAIL:
            case ColumnConstant.Member.OUT_NO:
            case ColumnConstant.Member.EMPLOYEE_ID:
            case Constants.WECHAT_IMPORT:
                break;
            default:
                return ServiceResult.fail("invalid primary_key");
        }
        // 获取账号信息

        //生成导入记录
        initImportRecord(buildMemberImportRecord(param, importRecordId));
        // 获取文件名称
        String fileName = "";
        if (StrUtil.isNotEmpty(fileName)) {
            importRecordDao.updateImportRecordFileName(param.getRecordId(), fileName);
            if (fileName.toLowerCase().contains("xlsx")) {
                // 根据excel导入
                ServiceResult<String> result = importMemberExcel(param);
                if (result.isSuccess()) {
                    return ServiceResult.success(importRecordId);
                } else {
                    // 导入失败
                    importService.updateImportRecordErrorMessage("-1", "导入失败",
                            importRecordId, ImportRecordStatusEnum.IMPORT_RECORD_STATUS_FAILED.getIndex());
                    // 删除redis中的进度 key

                    // 删除redis中的锁

                    return ServiceResult.fail("导入失败");
                }
            }
            importRecordDao.updateRecordStatus(importRecordId, ImportRecordStatusEnum.IMPORT_RECORD_STATUS_FAILED.getIndex());
            return ServiceResult.fail("不支持的文件类型");
        }
        return ServiceResult.fail("文件名不存在");
    }

    /**
     * 根据文件流 导入联系人数据
     *
     * @param param
     * @return
     */
    public ServiceResult<String> ImportMember(ImportMemberParam param) {
        // 先检查是否存在已有的任务
        String key = genImportLockKey(param.getOrgId());
        // 预先生成导入记录 ObjectId
        String importRecordId = genImportRecordId();
        param.setImportRecordId(importRecordId);
        // 检查是否有导入任务
        if (checkImportTask(key)) {
            return ServiceResult.<String>fail("当前已有联系人的导入任务正在执行中，请稍后再试")
                    .setErrorCode(String.valueOf(AppResourceErrorCode.TASK_WORKING));
        }
        // 加锁
        lockImport(key, importRecordId);
        // redis 锁key
        param.setKey(key);
        // 校验参数
        String message = dealUpdateMethod(param);
        // 对主键进行校验
        if (StrUtil.isNotEmpty(message)) {
            // 失败的话 先删除锁
            removeLock(key);
            return ServiceResult.<String>fail(message)
                    .setErrorCode(String.valueOf(AppRequestErrorCode.PARAM_FORBIDDEN));
        }

        // fix 20210917 只有个人联系人才进行限额
        Integer createMemberNum = -1;
        if (param.getAttribute() == MemberTypeEnum.INDIVIDUAL.getIndex()) {
            // 获取可以创建的联系人人数
            ServiceResult<Integer> userPackageQuota =
                    xmMemberClient.getUserPackageQuota(param.getOrgId());
            if (!userPackageQuota.isSuccess()) {
                // 失败的话 先删除锁
                removeLock(key);
                return ServiceResult.<String>fail("服务调用失败").setErrorCode("-1");
            }
            createMemberNum = userPackageQuota.getData();
        }
        param.setSaveMemberNum(createMemberNum);

        MultipartFile files = param.getFiles();
        if (ObjectUtil.isEmpty(files)) {
            log.error("导入的文件不存在");
            // 失败的话 先删除锁
            removeLock(key);
            return ServiceResult.<String>fail("invalid file")
                    .setErrorCode(String.valueOf(AppRequestErrorCode.PARAM_FORBIDDEN));
        }
        // 初始化参数
        initParams(param);
        if (!files.isEmpty()) {
            // 获取文件名称
            String fileName = files.getOriginalFilename();
            if (StrUtil.isNotEmpty(fileName)) {
                // 若存在文件名 则更新导入记录中的文件名
                importRecordDao.updateImportRecordFileName(param.getImportRecordId(), fileName);
            }
            String fileType = FileUtil.getFileExtension(fileName);
            if (!"xlsx".equals(fileType)) {
                // 文件类型不正确
                log.error("【member excel 导入】文件类型不正确 期望是 {} 实际是 {}", "xlsx", fileType);
                importRecordDao.updateRecordStatus(importRecordId, ImportRecordStatusEnum.IMPORT_RECORD_STATUS_FAILED.getIndex());
                importRecordDao.insertRecordDeatil(importRecordId, -1, "不支持的导入类型");
                // 解锁
                removeLock(key);
                return ServiceResult.<String>fail("不支持的导入类型").setErrorCode(String.valueOf(ValidationErrorCodeEnum.FILE_TYPE_NOT_RIGHT.getCode()));
            }
            // 获取文件大小
            long size = files.getSize();
            if (!FileUtil.checkFileSize(size, 10, FileUtil.M)) {
                // 限制10M
                log.error("【member excel 导入】文件过大");
                importRecordDao.updateRecordStatus(importRecordId, ImportRecordStatusEnum.IMPORT_RECORD_STATUS_FAILED.getIndex());
                importRecordDao.insertRecordDeatil(importRecordId, -1, "文件过大");
                // 解锁
                removeLock(key);
                return ServiceResult.<String>fail("文件过大").setErrorCode(String.valueOf(ResourceErrorCodeEnum.FILE_TOO_LARGE.getCode()));
            }
            // 根据excel导入
            InputStream inputStream = null;
            try {
                inputStream = files.getInputStream();
            } catch (IOException e) {
                log.error("读取文件失败", e);
                // 更新记录状态为失败
                importRecordDao.updateRecordStatus(importRecordId, ImportRecordStatusEnum.IMPORT_RECORD_STATUS_FAILED.getIndex());
                importRecordDao.insertRecordDeatil(importRecordId, -1, "读取文件失败");
                // 解锁
                removeLock(key);
                return ServiceResult.<String>fail("读取文件异常").setErrorCode("-1");
            }
            param.setInputStream(inputStream);
            AsyncUtil.threadPoolExecutor.execute(() -> importMemberExcel(param));
        } else {
            // 文件为空
            // 更新记录状态为失败
            importRecordDao.updateRecordStatus(importRecordId, ImportRecordStatusEnum.IMPORT_RECORD_STATUS_FAILED.getIndex());
            importRecordDao.insertRecordDeatil(importRecordId, -1, "文件为空");
            // 解锁
            removeLock(key);
            return ServiceResult.<String>fail("文件为空").setErrorCode("-1");
        }
        return ServiceResult.success(importRecordId);
    }

    /**
     * 初始化参数
     *
     * @param param
     */
    private void initParams(ImportMemberParam param) {
        //生成导入记录
        initImportRecord(buildMemberImportRecord(param));
    }

    /**
     * 参数校验
     *
     * @param param
     */
    private String dealUpdateMethod(ImportMemberParam param) {
        if(StrUtil.isEmpty(param.getPrimaryKey())){
            return "invalid primary_key";
        }
        switch (param.getPrimaryKey()){
            case ColumnConstant.Member.MOBILE:
            case ColumnConstant.Member.EMAIL:
            case ColumnConstant.Member.OUT_NO:
            case ColumnConstant.Member.EMPLOYEE_ID:
            case Constants.WECHAT_IMPORT:
                break;
            default:
                return "invalid primary_key";
        }
        return null;
    }


    /**
     * 生成导入记录id
     *
     * @return
     */
    public String genImportRecordId() {
        return StringUtil.getId();
    }

    /**
     * 检查是否有正在导入的记录
     *
     * @param key
     * @return
     */
    public Boolean checkImportTask(String key) {
        return redisService.hasKey(key);
    }

    /**
     * 加锁
     *
     * @param key
     * @param id
     */
    public void lockImport(String key, String id) {
        redisService.set(key, id, 3600, TimeUnit.SECONDS);
    }

    /**
     * 解锁
     *
     * @param key
     */
    public void removeLock(String key) {
        redisService.del(key);
    }

    public void initImportRecord(ImportRecordDo record) {
        importRecordDao.save(record);
    }

    /**
     * 生成联系人导入记录实体类
     *
     * @param param
     * @param id
     * @return
     */
    public ImportRecordDo buildMemberImportRecord(ImportMembersParam param, String id) {
        return buildImportRecord(id, param.getUserId(), param.getOrgId(), ImportRecordStatusEnum.IMPORT_RECORD_STATUS_PROCESSING.getIndex());
    }

    /**
     * 生成记录实体类
     *
     * @param param
     * @return
     */
    public ImportRecordDo buildMemberImportRecord(ImportMemberParam param) {
        return buildImportRecord(param.getImportRecordId(), param.getUserId(),
                param.getOrgId(), param.getAttribute());
    }

    /**
     * 生成记录实体类
     *
     * @param param
     * @return
     */
    public ImportRecordDo buildMProjectImportRecord(ImportProjectParam param) {
        return buildImportRecord(param.getImportRecordId(), param.getUserId(), param.getOrgId(), MemberTypeEnum.PROJECT.getIndex());
    }

    /**
     * 构建导入记录实体
     *
     * @param importRecordId
     * @param userId
     * @param orgId
     * @param mType
     * @return
     */
    public ImportRecordDo buildImportRecord(String importRecordId, String userId, String orgId, Integer mType) {
        ImportRecordDo record = new ImportRecordDo();
        record.setId(importRecordId);
        record.setOpUserId(userId);
        record.setStatus(ImportRecordStatusEnum.IMPORT_RECORD_STATUS_PROCESSING.getIndex());
        record.setOrgId(orgId);
        record.setMemberType(mType);
        record.setCreatedTime(DateUtil.getCommonDate());
        record.setUpdatedTime(record.getCreatedTime());
        return record;
    }

    /**
     * 导入excel
     *
     * @param param
     * @return
     */
    protected abstract ServiceResult<String> importMemberExcel(ImportMembersParam param);

    /**
     * 导入excel
     *
     * @param param
     */
    protected abstract void importMemberExcel(ImportMemberParam param);

    /**
     * 获取该组织下联系人的数量
     *
     * @param orgId
     * @param attribute
     * @return
     */
    public Long getMemberNumber(String orgId, Integer attribute) {
        // fix 20210917 只有个人联系人才判断数量
        if (attribute == MemberTypeEnum.INDIVIDUAL.getIndex()) {
            return memberDao.countIndividualMember(orgId);
        }
        return -1L;
    }

    /**
     * 校验组织id
     *
     * @param orgId
     * @return
     */
    public boolean checkOrganization(String orgId) {
        ServiceResult<OrganizationDTO> result =
                xmMemberClient.getOrgByIdOrCode(orgId, null);
        if (result.isSuccess()) {
            OrganizationDTO data = result.getData();
            if (ObjectUtil.isNotEmpty(data)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 检查主键列是否存在
     *
     * @param param
     * @param totalErrorMsgJson
     */
    public Boolean checkPrimaryColumn(ImportMembersParam param, JSONObject totalErrorMsgJson) {
        String primaryKey = PRIMARY_KEY_MAP.get(param.getPrimaryKey());
        if (!param.getHeaders().contains(primaryKey)) {
            totalErrorMsgJson.put("0", "主键列不存在");
            return false;
        }
        return true;
    }

    /**
     * 获取 扩展字段映射
     *
     * @param orgId
     * @param attribute
     * @return
     */
    public Map<String, FieldDTO> getFiledMap(String orgId, int attribute) {
        List<ExtendFieldDO> extendFields = extendFieldDao.listMemberExtendField(orgId, attribute);
        if (CollUtil.isNotEmpty(extendFields)) {
            Map<String, FieldDTO> fieldDTOMap = new HashMap<>(extendFields.size());
            extendFields.forEach(extendField -> {
                FieldDTO dto = new FieldDTO();
                dto.setId(extendField.getFieldId());
                dto.setType(extendField.getType());
                fieldDTOMap.put(extendField.getName(), dto);
            });
            return fieldDTOMap;
        }
        return new HashMap<>();
    }

    /**
     * 根据扩展字段信息获取扩展字段的相关枚举值
     *
     * @param filedMap
     * @return
     */
    public Map<String, List<ExtendFieldEnumItemDO>> getExtendFieldEnumMap(Map<String, FieldDTO> filedMap) {
        // 获取扩展字段下的枚举值
        if (CollUtil.isNotEmpty(filedMap)) {
            Set<String> fields = filedMap.keySet();
            List<String> ids =
                    fields.stream().map(id -> filedMap.get(id).getId()).collect(Collectors.toList());
            List<ExtendFieldEnumItemDO> extendFieldEnumItems =
                    extendFieldEnumItemDao.findByFieldIdsAndDisplayStatus(ids, 0);
            if (CollUtil.isNotEmpty(extendFieldEnumItems)) {
                Map<String, List<ExtendFieldEnumItemDO>> extendFieldEnumMap =
                        extendFieldEnumItems.stream().filter(item -> item.getExtendFieldId() != null).collect(Collectors.groupingBy(item -> item.getExtendFieldId()));
                return extendFieldEnumMap;
            }
        }
        return new HashMap<>();
    }


    /**
     * 根据组织id获取该组织下门店名称与id的映射
     *
     * @param orgId
     * @return
     */
    public Map<String, String> getStoreNameIdMap(String orgId) {
        ServiceResult<JSONObject> result = xmMemberClient.getStoreIdNameMap(orgId);
        if (!result.isSuccess()) {

        }
        JSONObject data = result.getData();
        if (!data.isEmpty()) {
            HashMap<String, String> storeNameIdMap = new HashMap<>(data.size());
            data.keySet().forEach(key -> {
                storeNameIdMap.put(data.getString(key), key);
            });
            return storeNameIdMap;
        }
        return new HashMap<>();
    }

    /**
     * 构建标签值字典
     *
     * @param orgId
     * @param attribute
     * @return
     */
    public Map<String, LabelDTO> getLabelMap(String orgId, Integer attribute) {
        if (attribute.equals(MemberTypeEnum.INDIVIDUAL.getIndex())) {
            List<LabelValueDO> labelValues = labelValueDao.listByOrgIdAndMemberType(orgId, attribute);
            if (CollUtil.isNotEmpty(labelValues)) {
                Map<String, List<LabelValueDO>> labelValueMap =
                        labelValues.stream().collect(Collectors.groupingBy(LabelValueDO::getLabelName));
                Map<String, LabelDTO> labelMap = new HashMap<>();
                labelValueMap.forEach((k, v) -> {
                    if (CollUtil.isNotEmpty(v)) {
                        LabelDTO dto = new LabelDTO();
                        dto.setId(v.get(0).getLabelId());
                        if (Objects.nonNull(v.get(0).getManual())) {
                            dto.setManual(v.get(0).getManual() == 1);
                        }
                        List<LabelValueDTO> values = new ArrayList<>(v.size());
                        v.forEach(vv -> {
                            LabelValueDTO labelValueDTO = new LabelValueDTO();
                            labelValueDTO.setId(vv.getLabelValueId());
                            labelValueDTO.setValue(vv.getValue());
                            values.add(labelValueDTO);
                        });
                        dto.setValues(values);
                        labelMap.put(k, dto);
                    }
                });
                return labelMap;
            }
        }
        return new HashMap<>();
    }

    /**
     * 获取excel表头
     *
     * @return
     * @throws IOException
     */
    public abstract List<String> getExcelHeaders() throws IOException;

    /**
     * 获取模板头
     *
     * @param orgId
     * @return
     */
    public abstract List<String> getHeader(String orgId);

    public List<Map<String, String>> buildExcelHeaders(String orgId) throws IOException {
        // 获取 需添加的表头文件
        List<String> headers = getHeader(orgId);
        // 获取 文件的表头
        List<String> excelHeaders = getExcelHeaders();
        // 从headers中删除已存在的列
        // 肯定是有值的
        if (CollectionUtil.isNotEmpty(excelHeaders)) {
            excelHeaders.forEach(h -> {
                // 从headers中删除已存在的列
                if (headers.contains(h)) {
                    headers.remove(h);
                }
            });
        }
        List<Map<String, String>> head = new ArrayList<>();
        if (CollUtil.isNotEmpty(headers)) {
            head = headers.stream().map(h -> {
                Map<String, String> map = new HashMap<>(2);
                map.put("head", h);
                return map;
            }).collect(Collectors.toList());
        }
        return head;
    }

    /**
     * 获取模板路径
     *
     * @return
     */
    public abstract String getTemplateExcelPath();

    /**
     * 获取模板路径
     *
     * @return
     */
    public abstract String getExcelTemplateName();

    /**
     * 填充模板
     *
     * @param headers
     * @return
     */
    public ServiceResult<String> fillTemplate(List<Map<String, String>> headers) {
        try {
            FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
            EasyExcel.write(getExcelTemplateName())
                    .withTemplate(getTemplateExcelPath())
                    .sheet(0)
                    .doFill(headers, fillConfig);
            return ServiceResult.success(getTemplateExcelPath());
        } catch (Exception e) {
            log.error("模板操做失败", e);
        }
        return ServiceResult.fail("模板操做失败");
    }

    public ServiceResult<String> downTemplate(String orgId) {
        try {
            // 构建表头数据
            List<Map<String, String>> headers = buildExcelHeaders(orgId);
            // 填充表头
            return fillTemplate(headers);
        } catch (Exception e) {
            log.error("模板操做失败", e);
        }
        return ServiceResult.fail("模板操做失败");
    }

    public void updateRecordData(String id, Integer receiveCount, Integer successCount, Integer failedCount, JSONArray data, Integer status) {
        importRecordDao.updateImportRecordData(id, receiveCount, successCount, failedCount, data, status);
    }
}
